#include "source_ps.h"
#include <panic.h>
#include <ps.h>
#include <string.h>
#include <boot.h>
#include "source_debug.h"

#ifdef DEBUG_PS
    #define PS_DEBUG(x) SOURCE_DEBUG(x)
#else
    #define PS_DEBUG(x)
#endif

#define VERSION_MAJOR 0
#define VERSION_MINOR 0
#define VERSION_PATCH 6

typedef enum
{
    SOURCE_PS_REMOTE_DEVICE_ADDR =  31,
    SOURCE_PS_SECONDARY_DEVICE_ADDR,
    SOURCE_PS_SETTING
} SOURCE_PS_KEY_T;

enum
{
    SETTING_AUTO_PAIR =  0,
    SETTING_DETACH_USB_WHILE_NO_DEVICE_CONNECTED,
    SETTING_INPUT_SOURCE_TYPE,
    SETTING_A2DP_CODEC_CONFIG,
};

typedef struct {
    unsigned NAP;
    unsigned LAP_hi;
    unsigned LAP_lo;
    unsigned UAP:8;
    unsigned padding:8;
} Remote_Device_Address_t;

typedef struct {
    Remote_Device_Address_t Device_Address;
} remote_device_address_config_def_t;

typedef struct
{
    unsigned fwUpgrade:1;
    unsigned a2dpCodecConfig:8;
    unsigned i2sLevel:8;
    unsigned spdifLevel:8;
    unsigned auxLevel:8;
    unsigned autoPowerOn:1;
    unsigned dummy:6;
} SETTING_T;

typedef struct
{
    SETTING_T setting;
} PS_DATA_T;

PS_DATA_T PS_RUNDATA;

void sourcePsInit(void)
{
    if (!sourcePsRead(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T)))
    {
        /* Default setting */
        PS_RUNDATA.setting.fwUpgrade         = TRUE;
        PS_RUNDATA.setting.a2dpCodecConfig   = 0x0E; /* Enable all codec default */
        PS_RUNDATA.setting.i2sLevel          = 9;
        PS_RUNDATA.setting.auxLevel          = 9;
        PS_RUNDATA.setting.spdifLevel        = 9;
        PS_RUNDATA.setting.autoPowerOn       = FALSE;

        sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
    }
}

/****************************************************************************
NAME
    ps_read_user - Read a User PSKey
*/
uint16 sourcePsRead(uint16 ps_key, void *ps_data, uint16 ps_size_in_byte)
{
    uint16 len;
    /* attempt to read from User PSKey */
    len = PsRetrieve(ps_key, ps_data, ps_size_in_byte/2);
    PS_DEBUG(("PS: Read key=%d, size=%d actual=%d\n", ps_key, ps_size, len));
    return len;
}

/****************************************************************************
NAME
    ps_write - Write a PSKey
*/
uint16 sourcePsWrite(uint16 ps_key, const void *ps_data, uint16 ps_size_in_byte)
{
    uint16 len;
    len = PsStore(ps_key, ps_data, ps_size_in_byte/2);
    PS_DEBUG(("PS: Write key=%d, size=%d actual=%d\n", ps_key, ps_size_in_byte, len));
    return len;
}

void sourcePsReadRemoteDeviceAddress(bdaddr *addr)
{
    remote_device_address_config_def_t bdaddr_remote_device;

    if (sourcePsRead(SOURCE_PS_REMOTE_DEVICE_ADDR, &bdaddr_remote_device, sizeof (remote_device_address_config_def_t)))
    {
        addr->lap = (((uint32)bdaddr_remote_device.Device_Address.LAP_hi << 16) | ((uint32) (bdaddr_remote_device.Device_Address.LAP_lo) & 0x0000FFFFUL));
        addr->uap = bdaddr_remote_device.Device_Address.UAP;
        addr->nap = bdaddr_remote_device.Device_Address.NAP;
    }
    else
    {
        addr->lap = 0;
        addr->uap  = 0;
        addr->nap = 0;
    }
}

void sourcePsWriteRemoteDeviceAddress(const bdaddr *addr)
{
    remote_device_address_config_def_t bdaddr_remote_device;

    bdaddr_remote_device.Device_Address.LAP_hi = (addr->lap>>16);
    bdaddr_remote_device.Device_Address.LAP_lo = (uint16)addr->lap;
    bdaddr_remote_device.Device_Address.NAP    = addr->nap;
    bdaddr_remote_device.Device_Address.UAP    = addr->uap;
    sourcePsWrite(SOURCE_PS_REMOTE_DEVICE_ADDR, &bdaddr_remote_device, sizeof(remote_device_address_config_def_t));
}

void sourcePsReadDualStreamSecondDeviceAddress(bdaddr *addr)
{
    remote_device_address_config_def_t bdaddr_remote_device;

    if (sourcePsRead(SOURCE_PS_SECONDARY_DEVICE_ADDR, &bdaddr_remote_device, sizeof (remote_device_address_config_def_t)))
    {
        addr->lap = (((uint32)bdaddr_remote_device.Device_Address.LAP_hi << 16) | ((uint32) (bdaddr_remote_device.Device_Address.LAP_lo) & 0x0000FFFFUL));
        addr->uap = bdaddr_remote_device.Device_Address.UAP;
        addr->nap = bdaddr_remote_device.Device_Address.NAP;
    }
    else
    {
        addr->lap = 0;
        addr->uap  = 0;
        addr->nap = 0;
    }
}

void sourcePsWriteDualStreamSecondDeviceAddress(const bdaddr *addr)
{
    remote_device_address_config_def_t bdaddr_remote_device;

    bdaddr_remote_device.Device_Address.LAP_hi = (addr->lap>>16);
    bdaddr_remote_device.Device_Address.LAP_lo = (uint16)addr->lap;
    bdaddr_remote_device.Device_Address.NAP    = addr->nap;
    bdaddr_remote_device.Device_Address.UAP    = addr->uap;
    sourcePsWrite(SOURCE_PS_SECONDARY_DEVICE_ADDR, &bdaddr_remote_device, sizeof(remote_device_address_config_def_t));
}

bool sourcePsGetFwUpgrade(void)
{
    return PS_RUNDATA.setting.fwUpgrade;
}

void sourcePsSetFwUpgrade(bool status)
{
    if(PS_RUNDATA.setting.fwUpgrade != status)
    {
        PS_RUNDATA.setting.fwUpgrade = status;

        sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
    }
}

void sourcePsGetVersion(uint8 *major, uint8 *minor, uint8 *patch)
{
    *major = VERSION_MAJOR;
    *minor = VERSION_MINOR;
    *patch = VERSION_PATCH;
}

void sourcePsGetCodecConfig(uint8 *aptxEnable, uint8 *aptxHDEnable, uint8 *aptxAdEnable, uint8 *aptxLLEnable)
{
    *aptxAdEnable = 0;
    *aptxEnable   = 0;
    *aptxHDEnable = 0;
    *aptxLLEnable = 0;

    if(PS_RUNDATA.setting.a2dpCodecConfig & 0x01)
    {
        *aptxAdEnable = 1;
    }

    if(PS_RUNDATA.setting.a2dpCodecConfig & 0x02)
    {
        *aptxEnable = 1;
    }

    if(PS_RUNDATA.setting.a2dpCodecConfig & 0x04)
    {
        *aptxHDEnable = 1;
    }

    if(PS_RUNDATA.setting.a2dpCodecConfig & 0x08)
    {
        *aptxLLEnable = 1;
    }
}

void sourcePsSetCodecConfig(bool aptxEnable, bool aptxHDEnable, bool aptxAdEnable, bool aptxLLEnable)
{
    uint8 a2dpCodecConfig = 0;

    a2dpCodecConfig = aptxAdEnable
                    | aptxEnable << 1
                    | aptxHDEnable << 2
                    | aptxLLEnable << 3;

    if(PS_RUNDATA.setting.a2dpCodecConfig != a2dpCodecConfig)
    {
        PS_RUNDATA.setting.a2dpCodecConfig = a2dpCodecConfig;
        sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
    }
}

uint8 sourcePsGetAudioLevel(AUDIO_SOURCE_T source_type)
{
    switch(source_type)
    {
    case AUDIO_SOURCE_ANALOG:
        return PS_RUNDATA.setting.auxLevel;
    case AUDIO_SOURCE_I2S:
        return PS_RUNDATA.setting.i2sLevel;
    case AUDIO_SOURCE_SPDIF:
        return PS_RUNDATA.setting.spdifLevel;
    default:
        return 9;
    }
}

void sourcePsSetAudioLevel(AUDIO_SOURCE_T source_type, uint8 level)
{
    switch(source_type)
    {
    case AUDIO_SOURCE_ANALOG:
        if(PS_RUNDATA.setting.auxLevel != level)
        {
            PS_RUNDATA.setting.auxLevel = level;
            sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
        }
        break;
    case AUDIO_SOURCE_I2S:
        if(PS_RUNDATA.setting.i2sLevel != level)
        {
            PS_RUNDATA.setting.i2sLevel = level;
            sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
        }
        break;
    case AUDIO_SOURCE_SPDIF:
        if(PS_RUNDATA.setting.spdifLevel != level)
        {
            PS_RUNDATA.setting.spdifLevel = level;
            sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
        }
        break;
    default:
        break;
    }
}

void sourcePsReadDeviceSettings(uint8* data)
{
    data[SETTING_A2DP_CODEC_CONFIG]  = PS_RUNDATA.setting.a2dpCodecConfig;
}

void sourcePsHandleDeviceSetting(uint8* data)
{
    bool needReboot = FALSE;

    if(data[SETTING_A2DP_CODEC_CONFIG] != PS_RUNDATA.setting.a2dpCodecConfig)
    {
        PS_RUNDATA.setting.a2dpCodecConfig = data[SETTING_A2DP_CODEC_CONFIG];
        needReboot = TRUE;
    }

    sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));

    if(needReboot)
    {
        BootSetMode(BootGetMode());
        while(1);
    }
}

void sourcePsFactoryDefault(void)
{
    bdaddr addr = {0x00, 0x00, 0x00};

    PS_RUNDATA.setting.a2dpCodecConfig = 0x0F; /* Enable all codec default */
    PS_RUNDATA.setting.i2sLevel          = 9;
    PS_RUNDATA.setting.auxLevel          = 9;
    PS_RUNDATA.setting.spdifLevel        = 9;

    sourcePsWriteRemoteDeviceAddress(&addr);
    sourcePsWriteDualStreamSecondDeviceAddress(&addr);

    sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
}

bool sourcePsGetAutoPowerOn(void)
{
    return PS_RUNDATA.setting.autoPowerOn;
}

void sourcePsSetAutoPowerOn(bool autoPowerOn)
{
    if(PS_RUNDATA.setting.autoPowerOn != autoPowerOn)
    {
        PS_RUNDATA.setting.autoPowerOn = autoPowerOn;

        sourcePsWrite(SOURCE_PS_SETTING, &PS_RUNDATA.setting, sizeof(SETTING_T));
    }
}
